<?php

Raise::load('core.RaiseObject');
Raise::load('core.RaiseFile');
Raise::load('core.RaiseVariable');
Raise::load('core.RaisePoint');
Raise::load('core.Rectangle');
Raise::load('core.RaiseColor');

/**
 * RaiseImage class
 * represents an image using GD Library
 *
 * @author Sam-Mauris Yong <hellclanner at live dot com>
 * @license http://www.opensource.org/licenses/bsd-license New BSD License
 * @package Raise.Core.Imaging
 * @since 1.2
 */
class RaiseImage extends RaiseObject {

    /**
     * MIME JPEG Image Type
     */
    const JPEG = 'image/jpeg';

    /**
     * MIME PNG Image Type
     */
    const PNG = 'image/png';

    /**
     * MIME GIF Image Type
     */
    const GIF = 'image/gif';

    /**
     * MIME Windows Bitmap Image Type
     */
    const WBMP = 'image/vnd.wap.wbmp';

    /**
     * the internal image resource
     * @var resource
     */
    private $resource;

    /**
     * Create a new RaiseImage
     * @param RaiseFile|string|integer $sourceOrWidth
     * @param integer $height (Optional) If specified, the first argument is the $width. This argument must be an integer
     */
    public function __construct(){
        $args = func_get_args();
        switch(count($args)){
            case 1:
                if(RaiseVariable::getDataType($args[0], RaiseVariable::TYPESTRING)){
                    $args[0] = new RaiseFile($args[0]);
                }
                if($args[0] instanceof RaiseFile){
                    $this->resource = imagecreatefromstring($args[0]->getContent());
                }else{
                    throw new RaiseInvalidArgumentException('$source must be a file pathname or a RaiseFile object.');
                }
                break;
            case 2:
                if(RaiseVariable::isNumeric($args[0]) && RaiseVariable::isNumeric($args[1])){
                    $this->resource = imagecreatetruecolor($args[0], $args[1]);
                }else{
                    throw new RaiseInvalidArgumentException('$width and $height must be numeric.');
                }
                break;
        }
    }

    /**
     * Resize ratio aspect based on height
     * @param integer $height  The new height of the image
     */
    public function resizeToHeight($height){
        $ratio = $height / $this->height();
        $this->resize($ratio * $this->width(), $height);
    }

    /**
     * Resize ratio aspect based on width
     * @param integer $width  The new width of the image
     */
    public function resizeToWidth($width){
        $ratio = $width / $this->width();
        $this->resize($width, $ratio * $this->height());
    }

    /**
     * Resize the image
     * @param RaisePoint|integer $newSizeOrX
     * @param integer $y (optional)
     */
    public function resize($newSize, $y = false){
      if($y !== false){
          $newSize = new RaisePoint($newSize, $y);
      }

      $temp = imagecreatetruecolor($newSize->x(), $newSize->y());
      imagecopyresampled($temp, $this->resource, 0, 0, 0, 0, $newSize->x(), $newSize->y(), $this->width(), $this->height());
      $this->resource = $temp;
    }

    /**
     * Crop the image into the specified size
     * @param Rectangle $rectangle
     */
    public function crop($rectangle){
      $temp = imagecreatetruecolor($rectangle->width(), $rectangle->height());
      imagecopyresampled($temp, $this->resource, 0, 0, $rectangle->x(), $rectangle->y(), $rectangle->width(), $rectangle->height(), $rectangle->width(), $rectangle->height());
      $this->resource = $temp;
    }

    /**
     * Resize and Crop the image to the width and height specified
     * @param integer $width
     * @param integer $height
     */
    public function resizeAndCrop($width, $height){
        if($this->width() < $this->height()){
            $this->resizeToWidth($width);
        }else{
            $this->resizeToHeight($height);
        }
        $this->crop(new Rectangle(new RaisePoint(0, 0), $width, $height));
    }

    /**
     * Rotate the image, and fill the empty space with a color
     * @param integer $degs The rotation clockwise in degrees
     * @param RaiseColor $color
     */
    public function rotate($degs, $color){
        if($color instanceof RaiseColor){
            $color = imagecolorallocate($this->resource, $color->red(), $color->green(), $color->blue());
        }
        $this->resource = imagerotate($this->resource, $degs, $color);
    }

    /**
     * Flip the image horizontally or vertically
     * @param boolean $flipHorizontal (Optional) Set whether to flip horizontally or not. Defaults to TRUE.
     * @param boolean $flipVertical (Optional) Set whether to flip vertically or not. Defaults to FALSE.
     */
    public function flip($flipHorizontal = true, $flipVertical = false){

        $src_x = 0;
        $src_y = 0;
        $src_width = $this->width();
        $src_height = $this->height();

        if($flipHorizontal){
            $src_y = $this->height();
            $src_height = -$this->height();
        }
        if($flipVertical){
            $src_x = $this->width();
            $src_width = -$this->width();
        }

        $temp = imagecreatetruecolor($this->width(), $this->height());
        imagecopyresampled($temp, $this->resource, 0, 0, $src_x, $src_y, $this->width(), $this->height(), $src_width, $src_height);
        $this->resource = $temp;
    }

    /**
     * Paste an RaiseImage onto this RaiseImage at a specific coordinate
     * @param RaiseImage $img The image to paste
     * @param Point The coordinates of the $img to paste at on this image.
     */
    public function paste($img, $point){
        if(!($img instanceof RaiseImage)){
            throw new RaiseInvalidArgumentException('Invalid argument $img. $img must be a RaiseImage object');
        }
        imagecopyresampled($img->resource, $this->resource, 0, 0, $point->x(), $point->y(), $img->width(), $img->height(), $img->width(), $img->height());
    }

    /**
     * Write the image to output (to client) and set header Content-Type as the image type.
     * @param int $type (optional) The type of image to output as (using constants from RaiseImage)
     */
    public function output($type = RaiseImage::JPEG){
        switch($type){
            case RaiseImage::JPEG:
                header('Content-Type: '.RaiseImage::JPEG);
                imagejpeg($this->resource);
                break;
            case RaiseImage::GIF:
                header('Content-Type: '.RaiseImage::GIF);
                imagegif($this->resource);
                break;
            case RaiseImage::PNG:
                header('Content-Type: '.RaiseImage::PNG);
                imagepng($this->resource);
                break;
            case RaiseImage::WBMP:
                header('Content-Type: '.RaiseImage::PNG);
                imagewbmp($this->resource);
                break;
            default:
                throw new RaiseInvalidArgumentException('Invalid Image Type specified. Use constants from RaiseImage for $type.');
                break;
        }
    }
    
    /**
     * Output the Image to a file
     * @param int $type The type of image to output as (using constants from RaiseImage)
     * @param string|RaiseFile $fileName
     */
    public function outputFile($type, $fileName){
        if(is_object($fileName) && $fileName instanceof RaiseFile){
            $fileName = $fileName->getPathName();
        }
        if(!RaiseVariable::getDataType($fileName, RaiseVariable::TYPESTRING)){
            throw new RaiseInvalidArgumentException('Invalid argument $fileName. The argument has to be either a string variable or RaiseFile object.');
        }
        switch($type){
            case RaiseImage::JPEG:
                imagejpeg($this->resource, $fileName);
                break;
            case RaiseImage::GIF:
                imagegif($this->resource, $fileName);
                break;
            case RaiseImage::PNG:
                imagepng($this->resource, $fileName);
                break;
            case RaiseImage::WBMP:
                imagewbmp($this->resource, $fileName);
                break;
            default:
                throw new RaiseInvalidArgumentException('Invalid Image Type specified. Use constants from RaiseImage for $type.');
                break;
        }
    }

    /**
     * Get the color at a specific point of the image
     * @param RaisePoint $point The XY coordinate in pixels
     */
    public function samplePixel($point){
        $idx = imagecolorat($this->resource, $point->x(), $point->y());
        $a = imagecolorsforindex($this->resource, $idx);
        return new RaiseColor($a['red'], $a['green'], $a['blue'], $a['alpha']);
    }

    /**
     * Get the image width in pixels
     * return integer
     */
    public function width(){
        return imagesx($this->resource);
    }

    /**
     * Get the image height in pixels
     * return integer
     */
    public function height(){
        return imagesy($this->resource);
    }

    /**
     * Get the width / height ratio of this image
     * @return double
     */
    public function ratio(){
        return $this->width() / $this->height();
    }

}